package com.wande.dataplatform.filecollection.security;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.digest.DigestUtil;
import com.wande.dataplatform.filecollection.common.enums.FileCollectionErrorCode;
import com.wande.dataplatform.filecollection.common.exception.FileCollectionException;
import com.wande.dataplatform.filecollection.config.FileCollectionProperties;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;

/**
 * 文件安全验证器
 * 提供文件类型、大小、MD5校验等安全控制
 *
 * @author wande
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class FileSecurityValidator {

    private final FileCollectionProperties properties;

    /**
     * 允许的文件类型集合
     */
    private Set<String> allowedTypes;

    /**
     * 初始化允许的文件类型
     */
    private void initAllowedTypes() {
        if (allowedTypes == null) {
            String types = properties.getUpload().getAllowedTypes();
            if (StrUtil.isNotBlank(types)) {
                allowedTypes = new HashSet<>(Arrays.asList(types.toUpperCase().split(",")));
            } else {
                allowedTypes = new HashSet<>();
            }
        }
    }

    /**
     * 验证文件类型
     *
     * @param fileName 文件名
     * @return 是否合法
     */
    public boolean validateFileType(String fileName) {
        initAllowedTypes();
        
        if (StrUtil.isBlank(fileName)) {
            return false;
        }
        
        String extension = FileUtil.extName(fileName).toUpperCase();
        boolean valid = allowedTypes.contains(extension);
        
        if (!valid) {
            log.warn("File type not allowed: {}", extension);
        }
        
        return valid;
    }

    /**
     * 验证文件类型（抛出异常）
     *
     * @param fileName 文件名
     * @throws FileCollectionException 如果文件类型不合法
     */
    public void validateFileTypeOrThrow(String fileName) {
        if (!validateFileType(fileName)) {
            String extension = FileUtil.extName(fileName);
            throw new FileCollectionException(FileCollectionErrorCode.FILE_TYPE_NOT_ALLOWED,
                "文件类型不允许: " + extension + "，允许的类型: " + properties.getUpload().getAllowedTypes());
        }
    }

    /**
     * 验证文件大小
     *
     * @param fileSize 文件大小（字节）
     * @return 是否合法
     */
    public boolean validateFileSize(long fileSize) {
        long maxSize = properties.getUpload().getMaxFileSize();
        boolean valid = fileSize > 0 && fileSize <= maxSize;
        
        if (!valid) {
            log.warn("File size exceeds limit: {} bytes, max: {} bytes", fileSize, maxSize);
        }
        
        return valid;
    }

    /**
     * 验证文件大小（抛出异常）
     *
     * @param fileSize 文件大小（字节）
     * @throws FileCollectionException 如果文件大小超限
     */
    public void validateFileSizeOrThrow(long fileSize) {
        if (!validateFileSize(fileSize)) {
            long maxSize = properties.getUpload().getMaxFileSize();
            throw new FileCollectionException(FileCollectionErrorCode.FILE_SIZE_EXCEEDED,
                String.format("文件大小超限: %d bytes，最大允许: %d bytes (%.2f MB)", 
                    fileSize, maxSize, maxSize / 1024.0 / 1024.0));
        }
    }

    /**
     * 验证MultipartFile
     *
     * @param file 上传文件
     * @throws FileCollectionException 如果验证失败
     */
    public void validateMultipartFile(MultipartFile file) {
        if (file == null || file.isEmpty()) {
            throw new FileCollectionException(FileCollectionErrorCode.FILE_EMPTY,
                "文件为空");
        }
        
        String fileName = file.getOriginalFilename();
        validateFileTypeOrThrow(fileName);
        validateFileSizeOrThrow(file.getSize());
    }

    /**
     * 验证File
     *
     * @param file 文件
     * @throws FileCollectionException 如果验证失败
     */
    public void validateFile(File file) {
        if (file == null || !file.exists()) {
            throw new FileCollectionException(FileCollectionErrorCode.FILE_NOT_FOUND,
                "文件不存在");
        }
        
        if (!file.isFile()) {
            throw new FileCollectionException(FileCollectionErrorCode.FILE_NOT_FOUND,
                "不是有效的文件");
        }
        
        validateFileTypeOrThrow(file.getName());
        validateFileSizeOrThrow(file.length());
    }

    /**
     * 计算文件MD5
     *
     * @param file 文件
     * @return MD5值
     */
    public String calculateMd5(File file) {
        try {
            return DigestUtil.md5Hex(file);
        } catch (Exception e) {
            log.error("Failed to calculate MD5 for file: {}", file.getName(), e);
            throw new FileCollectionException(FileCollectionErrorCode.FILE_MD5_ERROR,
                "计算文件MD5失败: " + e.getMessage());
        }
    }

    /**
     * 计算文件MD5
     *
     * @param inputStream 输入流
     * @return MD5值
     */
    public String calculateMd5(InputStream inputStream) {
        try {
            return DigestUtil.md5Hex(inputStream);
        } catch (Exception e) {
            log.error("Failed to calculate MD5 from input stream", e);
            throw new FileCollectionException(FileCollectionErrorCode.FILE_MD5_ERROR,
                "计算文件MD5失败: " + e.getMessage());
        }
    }

    /**
     * 验证文件MD5
     *
     * @param file 文件
     * @param expectedMd5 期望的MD5值
     * @return 是否匹配
     */
    public boolean validateMd5(File file, String expectedMd5) {
        if (StrUtil.isBlank(expectedMd5)) {
            return true;
        }
        
        String actualMd5 = calculateMd5(file);
        boolean valid = actualMd5.equalsIgnoreCase(expectedMd5);
        
        if (!valid) {
            log.warn("MD5 mismatch for file: {}, expected: {}, actual: {}", 
                file.getName(), expectedMd5, actualMd5);
        }
        
        return valid;
    }

    /**
     * 验证文件MD5（抛出异常）
     *
     * @param file 文件
     * @param expectedMd5 期望的MD5值
     * @throws FileCollectionException 如果MD5不匹配
     */
    public void validateMd5OrThrow(File file, String expectedMd5) {
        if (!validateMd5(file, expectedMd5)) {
            throw new FileCollectionException(FileCollectionErrorCode.FILE_MD5_MISMATCH,
                "文件MD5校验失败");
        }
    }

    /**
     * 检查文件是否安全（综合检查）
     *
     * @param file 文件
     * @return 是否安全
     */
    public boolean isFileSafe(File file) {
        try {
            validateFile(file);
            return true;
        } catch (Exception e) {
            log.warn("File security check failed: {}", file.getName(), e);
            return false;
        }
    }

    /**
     * 获取允许的文件类型列表
     *
     * @return 文件类型列表
     */
    public Set<String> getAllowedTypes() {
        initAllowedTypes();
        return new HashSet<>(allowedTypes);
    }

    /**
     * 获取最大文件大小
     *
     * @return 最大文件大小（字节）
     */
    public long getMaxFileSize() {
        return properties.getUpload().getMaxFileSize();
    }

    /**
     * 格式化文件大小
     *
     * @param size 文件大小（字节）
     * @return 格式化后的字符串
     */
    public String formatFileSize(long size) {
        if (size < 1024) {
            return size + " B";
        } else if (size < 1024 * 1024) {
            return String.format("%.2f KB", size / 1024.0);
        } else if (size < 1024 * 1024 * 1024) {
            return String.format("%.2f MB", size / 1024.0 / 1024.0);
        } else {
            return String.format("%.2f GB", size / 1024.0 / 1024.0 / 1024.0);
        }
    }
}
